package com.heima.schedule.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.heima.model.schedule.dto.Task;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.redis.CacheService;
import com.heima.schedule.service.TaskService;
import com.heima.schedule.service.TaskinfoLogsService;
import com.heima.schedule.service.TaskinfoService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

@Service
public class TaskServiceImpl implements TaskService {

    @Autowired
    private TaskinfoService taskinfoService;

    @Autowired
    private TaskinfoLogsService taskinfoLogsService;

    @Autowired
    private CacheService cacheService;

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Long addTask(Task task) {
        // 目标：把传进来Task对象，写入到数据库中，然后判断task中任务执行时间是否到了，如果到了就写一份到redis

        // 1. 参数校验
        if (task == null) {
            return null;
        }

        // 2. 插入taskInfo表
        // TaskInfo数据来源于Task，executeTime字段要额外处理一下
        Taskinfo taskinfo = new Taskinfo();
        BeanUtils.copyProperties(task, taskinfo);
        long executeTime = task.getExecuteTime();
        taskinfo.setExecuteTime(new Date(executeTime));
        boolean taskInfoSaveResult = taskinfoService.save(taskinfo);
        if (!taskInfoSaveResult) {
            return null;
        }

        // 3. 插入taskInfoLog表
        // TaskinfoLogs数据来源于Task,taskId,executeTime,status要额外处理一下
        // 0初始化,1已执行,2已取消
        TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
        BeanUtils.copyProperties(task, taskinfoLogs);
        taskinfoLogs.setTaskId(taskinfo.getTaskId());
        taskinfoLogs.setStatus(0);
        taskinfoLogs.setExecuteTime(new Date(executeTime));
        boolean logSaveResult = taskinfoLogsService.save(taskinfoLogs);
        if (!logSaveResult) {
            throw new RuntimeException("任务记录表插入失败");
        }

        // 4. 将Task写入缓存备用,taskId需要额外处理一下
        task.setTaskId(taskinfo.getTaskId());
        long currentTime = System.currentTimeMillis();
        if (currentTime >= executeTime) {
            // 参数一：列表名称
            // 参数二：写入队列的数据
            cacheService.lLeftPush("queue", JSON.toJSONString(task));
        }

        // 5. 返回任务id
        return taskinfo.getTaskId();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Boolean cancelTask(Long taskId) {
        // 1.入参判断
        if (taskId == null) {
            return false;
        }

        // 2. 删除 taskinfo 的记录
        boolean removeTaskInfoResult = taskinfoService.removeById(taskId);
        if (!removeTaskInfoResult) {
            return false;
        }

        // 3. 更新 taskinfolog 状态为已取消
        // 先查再修改
        TaskinfoLogs logs = taskinfoLogsService.getById(taskId);
        if (logs != null) {
            logs.setStatus(2);
            boolean updateResult = taskinfoLogsService.updateById(logs);
            if (!updateResult) {
                throw new RuntimeException("日志表更新失败");
            }
        }

        // 4. 组装Task结构，用于到redis队列中匹配数据
        // 数据来源于前面查出来的 taskinfolog
        // excuteTime字段需要处理一下
        Task task = new Task();
        BeanUtils.copyProperties(logs, task);
        Date executeTime = logs.getExecuteTime();
        task.setExecuteTime(executeTime.getTime());

        String s = JSON.toJSONString(task);

        // 4. 删除 redis 队列中的数据
        // 参数一：删除数据所在的队列名
        // 参数二：索引
        // 参数三：指定删除哪个value
        cacheService.lRemove("queue", 0, s);

        return true;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Task pollTask() {
        // 1. 从queue队列弹出数据，如果没有数据可以弹出，则不往下执行
        String rs = cacheService.lRightPop("queue");
        // 参数一：转换的字符串
        // 参数二：字符串转换成什么类型
        Task task = JSON.parseObject(rs, Task.class);
        if (task == null) {
            return null;
        }

        Long taskId = task.getTaskId();
        // 2. 根据任务id，删除数据库中 TaskInfo 数据
        boolean removeTaskInfoResult = taskinfoService.removeById(taskId);
        if (!removeTaskInfoResult) {
            return null;
        }

        // 3. 根据Redis中取出的数据，更新TaskInfoLogs表
        TaskinfoLogs logs = new TaskinfoLogs();
        logs.setStatus(1);
        logs.setTaskId(taskId);
        logs.setExecuteTime(new Date(task.getExecuteTime()));
        logs.setParameters(task.getParameters());

        // 额外维护executeTime和status字段
        boolean updateResult = taskinfoLogsService.updateById(logs);
        if (!updateResult) {
            throw new RuntimeException("更新任务状态失败");
        }

        // 4. 返回Task数据
        return task;
    }

    // 0. 设置定时，使用@Scheduled注解
    // 设置1秒钟执行一次
    // fixedRate  -> 每隔多少毫秒执行一次这个方法
    // fixedDelay -> 上一次执行完后，间隔多少毫秒执行下一次方法
    // 每隔1000毫秒执行一次
    // fixedRate: 1,2,3,4,5,6,7...
    // fixedDelay: 1开始,3结束,4开始,7结束,8开始,11结束,12开始..
    @Scheduled(fixedRate = 1000)
    public void initData() {
        // 参数一：name（要锁数据，去重那个数据）
        // 参数二：过期时间
        String token = cacheService.tryLock("FUTURE_TASK_SYNC", 1000 * 30);
        if(StringUtils.isBlank(token)){
            return;
        }

        System.out.println("任务执行了一次：" + LocalDateTime.now());
        // 1. 清除缓存中所有任务
        cacheService.delete("queue");

        // 2. 从数据库中查询所有任务
        // 注意：此时还存在于TaskInfo表中的所有任务，是未执行的任务
        List<Taskinfo> list = taskinfoService.list();
        if(CollectionUtil.isEmpty(list)) {
            return;
        }

        // 3. 遍历任务，逐个判断执行时间
        // 如果执行时间小于当前时间，就加载到Redis中
        for (Taskinfo taskinfo : list) {
            if(taskinfo == null) {
                continue;
            }

            Date executeTime = taskinfo.getExecuteTime();
            long currentTime = System.currentTimeMillis();
            if(currentTime >= executeTime.getTime()) {
                Task task = new Task();
                BeanUtils.copyProperties(taskinfo, task);

                cacheService.lLeftPush("queue", JSON.toJSONString(task));
            }
        }
    }
}
